﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using CoolCode.Linq.Expressions;

namespace CoolCode.UnitTest
{ 
    public static class Rand
    {
        private static readonly Random rand = new Random();
        private static readonly Dictionary<Type, Delegate> randCache = new Dictionary<Type, Delegate>();
        private static readonly Type[] supportedTypes = {
            typeof(byte),typeof(short), typeof(int), typeof(long),typeof(sbyte), typeof(ushort), typeof(uint),
            typeof(ulong), typeof(float), typeof(double), typeof(decimal),typeof(char), typeof(string),
            typeof(DateTime), typeof(Guid)
        };

        private static readonly Func<Type, object> GetRandomValue = (type) =>
        {
            if (randCache.ContainsKey(type))
            {
                Delegate dlg = randCache[type];
                ParameterInfo[] parameterInfo = dlg.Method.GetParameters();
                if (parameterInfo.Length == 2)
                    return dlg.DynamicInvoke(rand);
                else
                    return dlg.DynamicInvoke();
            }
            return null;
        };

        static Rand()
        {
            supportedTypes.ForEach(type => AddToCache(type));
        }

        public static T New<T>()
        {
            return (T)New(typeof(T));
        }

        public static object New(Type type)
        {
            return New(type, GetRandomValue);
        }

        public static IEnumerable<T> New<T>(int count)
        {
            return New(typeof(T), count).Cast<T>();
        }

        public static IEnumerable<object> New(Type type, int count)
        {
            for (int i = 0; i < count; i++)
                yield return New(type, GetRandomValue);
        }

        public static object New(Type type, Func<Type, object> getValue)
        {
            if (type.IsPrimitive())
            {
                return getValue(type);
            }
            object target = Activator.CreateInstance(type);
            foreach (var prop in type.GetProperties())
            {
                if (prop.CanWrite)
                {
                    if (prop.PropertyType.IsPrimitive())
                    {
                        prop.SetValue(target, getValue(prop.PropertyType), null);
                    }
                    else if (prop.PropertyType.IsNullableType())
                    {
                        prop.SetValue(target, Enum(null, getValue(prop.PropertyType.GetNonNullableType())), null);
                    }
                    else
                    {
                        prop.SetValue(target, New(prop.PropertyType, getValue), null);
                    }
                }
            }
            return target;
        }

        public static T Numeric<T>()
        {
            return (T)Numeric(typeof(T));
        }

        public static object Numeric(Type type)
        {
            FieldInfo maxValueField = type.GetField("MaxValue", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static);
            object max = maxValueField.GetValue(null);
            Type randType = typeof(Random);
            ParameterExpression randExpr = Expression.Parameter(randType, "rand");
            ParameterExpression paramExpr = Expression.Parameter(type, "maxValue");

            MethodCallExpression nextDoubleExpr = Expression.Call(randExpr, randType.GetMethod("NextDouble"));
            Expression bodyExpr = Expression.Convert(
                Expression.Multiply(nextDoubleExpr, Expression.Convert(paramExpr, typeof(double))),
                type);
            var resultExpr = Expression.Lambda(bodyExpr, randExpr, paramExpr);
            var randomNumeric = resultExpr.Compile();
            return randomNumeric.DynamicInvoke(rand, max);
        }

        public static T Enum<T>(params T[] list)
        {
            if (list.Length == 0) throw new ArgumentException("The length of list must greater than zero!");
            int randIndex = rand.Next(0, list.Count());
            return list.ElementAt(randIndex);
        }

        private static void AddToCache(Type type)
        {
            TypeCode code = Type.GetTypeCode(type);
            Delegate randomDelegate = null;

            switch (code)
            {
                case TypeCode.Byte:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.SByte:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64:
                case TypeCode.Single:
                case TypeCode.Double:
                case TypeCode.Decimal:
                    randomDelegate = RandomNumericDelegate(type);
                    break;
                case TypeCode.Boolean:
                    randomDelegate = Self.Func(() => Enum(true, false));
                    break;
                case TypeCode.Char:
                    randomDelegate = Self.Func(() => randChar());
                    break;
                case TypeCode.String:
                    randomDelegate = Self.Func(() => randString());
                    break;
                case TypeCode.DateTime:
                    randomDelegate = Self.Func(() => randDateTime()); 
                    break;
                //case TypeCode.Object:
                //    randomDelegate = Self.Func(() => Enum(new object(),null));
                //    break;                    
            }

            if (type == typeof(Guid))
            {
                randomDelegate = Self.Func(() => Guid.NewGuid());
            }

            if (randomDelegate != null)
            {
                randCache.Add(type, randomDelegate);
            }

        }

        private static char randChar()
        {
            return Convert.ToChar(rand.Next(65, 117));
        }

        private static string randString()
        {
            int maxCount = rand.Next(2, 20);
            char[] chars = new char[maxCount];
            for (int i = 0; i < maxCount; i++)
                chars[i] = randChar();
            return new string(chars);
        }

        private static DateTime randDateTime()
        {
            return DateTime.Now
                .AddDays(rand.Next(-1000, 1000))
                .AddHours(rand.Next(-100, 100))
                .AddMinutes(rand.Next(-1000, 1000))
                .AddMilliseconds(rand.Next(-10000, 10000));
        }

        private static Delegate RandomNumericDelegate(Type type)
        {
            FieldInfo maxValueField = type.GetField("MaxValue", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static);
            object max = maxValueField.GetValue(null);
            if (((IComparable)Convert.ToDouble(max)).CompareTo(1000000.0) > 0) max = 1000000;
            Type randType = typeof(Random);
            ParameterExpression randExpr = Expression.Parameter(randType, "rand");
            Expression maxValueExpr = Expression.Constant(max);

            MethodCallExpression nextDoubleExpr = Expression.Call(randExpr, randType.GetMethod("NextDouble"));
            Expression bodyExpr = Expression.Convert(
                Expression.Multiply(nextDoubleExpr, Expression.Convert(maxValueExpr, typeof(double))),
                type);
            var resultExpr = Expression.Lambda(bodyExpr, randExpr);
            var randomNumeric = resultExpr.Compile();
            return randomNumeric;
        }

    }
}
